/*
 * @Author: LVGRAPE
 * @LastEditors: LVGRAPE
 */
#include "AT32_Bootloader_UART_Protocol.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include "uart.h"
#include "errno.h"
#include <windows.h>

#define CMD_NUM 24
#define CMD_MAX_LEN 64

at32_bup_t at32bootloaderHandle;
int fcom = 0;
int isp_program_file(at32_bup_t *b, char *filename, uint32_t startAddr);
const char AT32_CMD_LIST[CMD_NUM][CMD_MAX_LEN] = {
    "--comport",                  // 0
    "--writefile",                // 1
    "--readfile",                 // 2
    "--help",                     // 3
    "--isp",                      // 4
    "--getCommands",              // 5
    "--getVersion",               // 6
    "--getDeviceID",              // 7
    "--readMemory",               // 8
    "--jump",                     // 9
    "--writeMemory",              // 10
    "--erase",                    // 11
    "--eraseAndProgramProtect",   // 12
    "--eraseAndProgramUnprotect", // 13
    "--accessProtect",            // 14
    "--accessUnprotect",          // 15
    "--firmwareCRC",              // 16
    "--enableSPIM",               // 17
    "--enablesLib",               // 18
    "--disablesLib",              // 19
    "--getsLibStatus",            // 20
    "--SPIMRemap",                // 21
    "--resetDevice",              // 22
    "--advancedAccessProtect"     // 23
};
const char at32_help_list[CMD_NUM][64] = {
    "--comport [COMx] [baudrate]",
    "--writefile [filename] [address] [size(optional)]",
    "--readfile [filename] [address] [size(optional)]",
    "Print help",
    "Set host identification number",
    "Get device command list",
    "Get bootloader version",
    "Get device ID",
    "[address] [size-1],Read max 256 memory at a given address",
    "Jump to a given memory address",
    "[address] [size-1] [data], Write to memory at a given address",
    "Erase a given memory area or the entire chip or blank",
    "Erase/program protection enable",
    "Erase/program protection disable",
    "Access protection enable",
    "Access protection disable",
    "Calculate sector CRC",
    "SPIM enable",
    "SLib enable",
    "SLib disable",
    "Get SLib status",
    "SPIM IO PIN remap",
    "Reset device",
    "Advanced access protection enable"};
void printHelp()
{
    printf("***************************   AT32 Bootloader Help *************************** \n");
    printf("WARNING: cmd will execute by input order! \n");
    for (int i = 0; i < CMD_NUM; i++)
    {
        printf("    %-32s: %s\n", AT32_CMD_LIST[i], at32_help_list[i]);
    }
}
char *strToUpper(char *str)
{
    for (int i = 0; str[i] != '\0'; i++)
    {
        if (str[i] >= 'a' && str[i] <= 'z')
        {
            str[i] = str[i] - 'a' + 'A';
        }
    }
    return str;
}
int strcmpIgoreLower(const char *str1, const char *str2)
{
    int ret = 0;
    while (ret == 0)
    {
        ret = toupper(*str1) - toupper(*str2);
        if (ret != 0 || *str1 == '\0' || *str2 == '\0')
        {
            return ret;
        }
        str1++;
        str2++;
        // if(*str1 == '\0' || *str2 == '\0')
        // {
        //     return toupper(*str1) - toupper(*str2);
        // }
    }
    return ret;
}
int isCmdExist(char *cmd)
{
    for (int i = 0; i < CMD_NUM; i++)
    {
        const char *p1 = cmd;
        const char *p2 = AT32_CMD_LIST[i];
        if (strcmpIgoreLower(p1, p2) == 0)
        {
            return i;
        }
    }
    return -1;
}
int executeCmd(int cmdNum, char *param[16], uint8_t paramCount)
{
    // printf("cmdNum:%d\n", cmdNum);
    switch (cmdNum)
    {
    case 0:
    { // printf("comport\n");
        uint32_t baudrate = 115200;
        // printHelp();
        if (paramCount == 2)
        {
            baudrate = atoi(param[1]);
        }
        if (uart_win_init(param[0], baudrate))
        // if (serial_init(&fcom, param[0], baudrate) != 0)
        {
            printf("serial_init failed\n");
            exit(1);
            return -1;
        }
    }
    break;
    case 1:
    {
        char *filename = param[0];
        uint32_t address = (uint32_t)strtol(param[1], NULL, 16);
        if (isp_program_file(&at32bootloaderHandle, filename, address))
            exit(1);
    }
    break;
    case 2:
        break;
    case 3:
        printHelp();
        break;
    case 4:
        // printf("set isp");
        if (at32_set_isp(&at32bootloaderHandle) != 0)
            exit(1);
        break;
    case 5:
        // printf("get cmd list");
        if (at32_get_commands(&at32bootloaderHandle) != 0)
            exit(1);
        break;
    case 6:
        // printf("get version");
        if (at32_get_version(&at32bootloaderHandle) != 0)
            exit(1);
        break;
    case 7:
        // printf("get device id");
        if (at32_get_device_id(&at32bootloaderHandle) != 0)
            exit(1);
        break;
    case 8:
        // printf("read memory");
        if (paramCount == 2)
        {
            uint32_t address = (uint32_t)strtol(param[0], NULL, 16);
            uint16_t len = (uint16_t)atoi(param[1]);
            uint8_t buff[256];
            // printf("address:0x%08X, len:%d\n", address, len);
            if (at32_read_memory(&at32bootloaderHandle, address, buff, len))
                exit(1);
        }
        else
        {
            printf("--readMemory [addr] [size-1], output size+1\n");
        }
        break;
    case 9:
        // printf("jump to address");
        if (paramCount == 1)
        {
            uint32_t address = (uint32_t)strtol(param[0], NULL, 16);
            if (at32_jump_to_address(&at32bootloaderHandle, address) != 0)
                exit(1);
        }
        else
        {
            printf("--jumpToAddress [addr]\n");
        }
        break;
    case 10:
        // printf("write memory");
        if (paramCount == 3)
        {
            uint32_t address = (uint32_t)strtol(param[0], NULL, 16);
            uint16_t len = (uint16_t)atoi(param[1]);
            uint8_t *data = (uint8_t *)malloc(len);
            memcpy(data, param[2], len);
            printf("address:0x%08X, len:%d\n", address, len);
            for (int i = 0; i < len; i++)
            {
                printf("%d [%c]:0x%02X \n", i, data[i], data[i]);
            }
            printf("\n");
            if (at32_write_memory(&at32bootloaderHandle, address, data, len))
                exit(1);
        }
        else
        {
            printf("--writeMemory [addr] [size-1] [\"data\"]\n");
        }
        break;
    case 11:
        // printf("erase\n");
        // int at32_erase_sector(at32_bup_t *b, uint16_t count, uint16_t *sectors);
        // int at32_earase_block(at32_bup_t *b, uint32_t blockAddr);
        // int at32_erase_bank(at32_bup_t *b, uint8_t type);

        if (paramCount == 3)
        {
            if (strcmpIgoreLower(param[0], "sector") == 0)
            {
                uint16_t count = (uint16_t)atoi(param[1]);
                uint16_t *sectors = (uint16_t *)malloc(count * sizeof(uint16_t));
                printf("sectorCount:%d\n", count);
                char *pStart = param[2];
                char *pEnd = pStart;
                for (int i = 0; i < count; i++)
                {
                    pEnd = strchr(pEnd, ',');
                    sectors[i] = (uint16_t)strtol(pStart, &pEnd, 16);
                    printf("sector[%d]:%d %s %s\n", i, sectors[i], pStart, pEnd);
                    pEnd++;
                    pStart = pEnd;
                }
                if (at32_erase_sector(&at32bootloaderHandle, count, sectors))
                    exit(1);
            }
            else
            {
                printf("--erase sector [sectorCount] [sector1,sector2, ...]\n");
            }
        }
        else if (paramCount == 2)
        {
            if (strcmpIgoreLower(param[0], "block") == 0)
            {
                uint32_t blockAddr = (uint32_t)strtol(param[1], NULL, 16);
                if (at32_earase_block(&at32bootloaderHandle, blockAddr))
                    exit(1);
            }
            else
            {
                printf("--erase block [blockAddr]\n");
            }
        }
        else if (paramCount == 1)
        {
            if (strcmpIgoreLower(param[0], "bank1") == 0)
            {
                if (at32_erase_bank(&at32bootloaderHandle, 2))
                    exit(1);
            }
            else if (strcmpIgoreLower(param[0], "bank2") == 0)
            {
                if (at32_erase_bank(&at32bootloaderHandle, 1))
                    exit(1);
            }
            else if (strcmpIgoreLower(param[0], "bank3") == 0)
            {
                if (at32_erase_bank(&at32bootloaderHandle, 0))
                    exit(1);
            }
            else if (strcmpIgoreLower(param[0], "allbank") == 0)
            {
                if (at32_erase_bank(&at32bootloaderHandle, 3))
                    exit(1);
            }
            else
            {
                printf("--erase bank1\n");
                printf("--erase bank2\n");
                printf("--erase bank3\n");
                printf("--erase allbank\n");
            }
        }
        else
        {
            printf("--erase sector [sectorCount] [sector1,sector2, ...]\n");
            printf("--erase block [blockAddr]\n");
            printf("--erase bank1\n");
            printf("--erase bank2\n");
            printf("--erase bank3\n");
            printf("--erase allbank\n");
        }
        break;
    case 12:
        // int at32_erase_and_program_protect(at32_bup_t *b, uint16_t secotorCount, uint8_t *sectors);
        if (paramCount == 2)
        {
            uint16_t count = (uint32_t)atoi(param[0]);
            uint8_t *sectors = (uint8_t *)malloc(count * sizeof(uint8_t));
            char *pStart = param[1];
            char *pEnd = pStart;
            for (int i = 0; i < count; i++)
            {
                pEnd = strcat(pEnd, ",");
                sectors[i] = (uint8_t)strtol(pStart, &pEnd, 16);
                pStart = pEnd;
            }
            if (at32_erase_and_program_protect(&at32bootloaderHandle, count, sectors))
                exit(1);
        }
        else
        {
            printf("--erase_and_program_protect sector [sectorCount] [sector1,sector2, ...]\n");
        }
        break;
    case 13:
        // int at32_erase_and_program_unprotect(at32_bup_t *b);
        if (at32_erase_and_program_unprotect(&at32bootloaderHandle))
            exit(1);
        break;
    case 14:
        // int at32_access_protect(at32_bup_t *b);
        if (at32_access_protect(&at32bootloaderHandle))
            exit(1);
        break;
    case 15:
        // int at32_access_unprotect(at32_bup_t *b);
        if (at32_access_unprotect(&at32bootloaderHandle))
            exit(1);
        break;
    case 16:
        // int at32_firmware_crc(at32_bup_t *b, uint32_t startAddress, uint16_t sectorCount, uint32_t *crc);
        if (paramCount == 2)
        {
            uint32_t startAddress = (uint32_t)strtol(param[0], NULL, 16);
            uint16_t sectorCount = (uint16_t)atoi(param[1]);
            uint32_t crc;
            if (at32_firmware_crc(&at32bootloaderHandle, startAddress, sectorCount, &crc))
                exit(1);
            printf("crc: 0x%08X\n", crc);
        }
        else
        {
            printf("--firmware_crc [startAddress] [sectorCount]\n");
        }
        break;
    case 17:
        // int at32_enable_SPIM(at32_bup_t *b, uint8_t type, uint32_t size, uint32_t fda);
        if (paramCount == 3)
        {
            uint8_t type = (uint8_t)atoi(param[0]);
            uint32_t size = (uint32_t)strtol(param[1], NULL, 16);
            uint32_t fda = (uint32_t)strtol(param[2], NULL, 16);
            if (at32_enable_SPIM(&at32bootloaderHandle, type, size, fda))
                exit(1);
        }
        else
        {
            printf("--enable_SPIM [type] [size(0xXXXX XXXX)] [fda(0xXXXX XXXX)]\n");
        }
        break;
    case 18:
        // int at32_enable_sLib(at32_bup_t *b, uint32_t password, uint16_t start, uint16_t data, uint16_t end);
        if (paramCount == 4)
        {
            uint32_t password = (uint32_t)strtol(param[0], NULL, 16);
            uint16_t start = (uint16_t)strtol(param[1], NULL, 16);
            uint16_t data = (uint16_t)strtol(param[2], NULL, 16);
            uint16_t end = (uint16_t)strtol(param[3], NULL, 16);
            if (at32_enable_sLib(&at32bootloaderHandle, password, start, data, end))
                exit(1);
        }
        else
        {
            printf("--enable_sLib [password(0xXXXX)] [start] [data] [end]\n");
        }
        break;
    case 19:
        // int at32_disable_sLib(at32_bup_t *b, uint32_t password);
        if (paramCount == 1)
        {
            uint32_t password = (uint32_t)strtol(param[0], NULL, 16);
            if (at32_disable_sLib(&at32bootloaderHandle, password))
                exit(1);
        }
        else
        {
            printf("--disable_sLib [password(0xXXXX XXXX)]\n");
        }
        break;
    case 20:
        // int at32_get_sLib_status(at32_bup_t *b, uint8_t *status);
        {
            uint8_t status[16];
            uint8_t ret = at32_get_sLib_status(&at32bootloaderHandle, status);
            if (ret == 0)
            {
                printf("sLib status: ");
                for (int i = 0; i < 16; i++)
                {
                    printf("0x%02X ", status[i]);
                }
                printf("\n");
            }
            else
            {
                printf("at32_get_sLib_status error\n");
            }
        }
        break;
    case 21:
        // int at32_SPIM_Remap(at32_bup_t *b, uint8_t remap);
        {
            uint8_t remap = (uint8_t)atoi(param[0]);
            at32_SPIM_Remap(&at32bootloaderHandle, remap);
        }
        break;
    case 22:
        // int at32_reset_device(at32_bup_t *b);
        at32_reset_device(&at32bootloaderHandle);
        break;
    case 23:
        // int at32_advanced_Access_protect(at32_bup_t *b, uint8_t *flag);
        if (paramCount == 1)
        {
            uint16_t flag = strtol(param[0], NULL, 16);
            at32_advanced_Access_protect(&at32bootloaderHandle, (uint8_t *)&flag);
        }
        else
        {
            printf("--advanced_Access_protect [flag(0xXXXX)]\n");
        }
        break;
    default:
        break;
    }
    return 0;
}
int bl_write(uint8_t *buff, uint16_t len)
{
    if (fcom == 0)
    {
        printf("bl_write error, f_com: 0x%08X\n", fcom);
        return 0;
    }
    int ret = write(fcom, buff, len);
    // tcflush(f_com, TCIOFLUSH);
    if (ret != len)
        printf("bl_write error, ret:%d\n", ret);
    return ret;
}
int bl_read(uint8_t *buff, uint16_t len)
{
    if (!fcom)
    {
        printf("bl_read error, f_com: 0x%08X\n", fcom);
        return 0;
    }
    int rlen = 0;
    int timeout = 0;
    while (1)
    {
        if (read(fcom, buff, 1) == 1)
        {
            rlen++;
            buff++;
            if (rlen == len)
            {
                break;
            }
            timeout = 0;
        }
        else
        {
            // usleep(10);
            // printf("waitting...%d\n", timeout);
            if (timeout++ > 30000)
                break;
        }
    }
    // return read(fcom, buff, len);
    return rlen;
}
int bl_clear_io()
{
    // serial_clear_buffer(fcom);
    return uart_win_clear();
}
void delay_ms(uint32_t ms)
{
    // usleep(ms * 1000);
    Sleep(ms);
}
int bl_hardwareReset()
{
    for (int i = 0; i < 2; i++)
    {
        // serial_set_rts(fcom, 0);
        uart_win_set_rts(fcom, 1);
        delay_ms(5);
        // serial_set_rts(fcom, 1);
        uart_win_set_rts(fcom, 0);
        delay_ms(5);
#if 1
        /**FIXME SOME PC CAN'T RX ANY DATA. While some pc won't work without it */
        /**FIXME PC NEED TO UPDATE CH340 DRIVER TO 2025 !!! */
        uart_win_set_rts(fcom, 1);
        delay_ms(5);
#endif
    }
    return 0;
}
int restToIspMode()
{
    printf("Use RTS to reset to ISP mode\n");
    // serial_set_rts(fcom, 0);
    uart_win_set_rts(fcom, 0);
    delay_ms(1000);
    bl_hardwareReset();
    return 0;
}

int main(int argc, char *argv[])
{
    /**NOTE 使用 "--"来区分指令*/

    at32bootloaderHandle.write = uart_win_write;
    at32bootloaderHandle.read = uart_win_read;
    at32bootloaderHandle.delay_ms = delay_ms;
    at32bootloaderHandle.restToIspMode = restToIspMode;
    at32bootloaderHandle.hardwareReset = bl_hardwareReset;
    at32bootloaderHandle.clearIo = uart_win_clear;
    at32bootloaderHandle.synced = 0;

    printf("\n********************** AT32 bootloader tool <%s:%s> **********************\n\n\n", __DATE__, __TIME__);
    struct
    {
        char *cmd;
        char *param[64];
        uint8_t paramCount;
    } cmdlist[64] = {0};
    uint8_t cmdlistCount = 0;
    uint8_t cmdParamCount = 0;
    for (int i = 0; i < argc; i++)
    {

        if (argv[i][0] == '-' && argv[i][1] == '-')
        {
            cmdlist[cmdlistCount].cmd = argv[i];
            cmdlist[cmdlistCount].paramCount = 0;
            cmdParamCount = 0;
            cmdlistCount++;
        }
        else
        {
            if (cmdlistCount != 0)
            {
                cmdlist[cmdlistCount - 1].param[cmdParamCount] = argv[i];
                cmdParamCount++;
                cmdlist[cmdlistCount - 1].paramCount = cmdParamCount;
            }
        }
    }
    // printf("cmdlistCount:%d\n", cmdlistCount);
    int existCmdcnt = 0;
    for (int i = 0; i < cmdlistCount; i++)
    {
        // printf("cmdlist[%d]:%s p:%d\n", i, cmdlist[i].cmd, cmdlist[i].paramCount);
        // if (cmdlist[i].paramCount != 0)
        // {
        //     for (int j = 0; j < cmdlist[i].paramCount; j++)
        //     {
        //         printf("\t %s\n", cmdlist[i].param[j]);
        //     }
        // }
        // printf("\n");

        int cmdNum = isCmdExist(cmdlist[i].cmd);
        if (cmdNum != -1)
        {

            // printf("cmdNum:%d %s\n", cmdNum, cmdlist[i].cmd);
            executeCmd(cmdNum, cmdlist[i].param, cmdlist[i].paramCount);
            existCmdcnt++;
        }
        else
        {
            printf("cmd:%s not exist\n", cmdlist[i].cmd);
        }
    }
    if (existCmdcnt == 0)
    {
        printHelp();
    }
}
int isp_program_file(at32_bup_t *b, char *filename, uint32_t startAddr)
{
    // if (!fcom)
    // {
    //     printf("isp_program_file error, f_com: 0x%08X\n", fcom);
    //     return -1;
    // }
    int file = open(filename, O_RDONLY);
    if (file < 0)
    {
        printf("open file %s error\n", filename, strerror(errno));
        return -2;
    }
    size_t totalSize = lseek(file, 0, SEEK_END);
    lseek(file, 0, SEEK_SET);
    uint8_t *buff = (uint8_t *)malloc(totalSize);
    read(file, buff, totalSize);
    close(file);
    size_t wroteSize = 0;
    size_t SizeLeft = totalSize;
    int thisWriteSize = 0;
    int packCount = totalSize / 128;
    int stepPercent = 0, stepPercentPre = 0;

    if (!b->synced)
    {
        at32_sync(b);
    }

    if (packCount < 100)
    {
        for (int i = 0; i <= packCount; i++)
        {
            printf("=");
        }
        printf("\n");
    }
    else
    {
        for (int i = 0; i < 100; i++)
        {
            printf("=");
        }
        printf("\n");
    }
    while (1)
    {
        thisWriteSize = SizeLeft > 128 ? 128 : SizeLeft;
        int ret = at32_write_memory(b, startAddr + wroteSize, buff + wroteSize, thisWriteSize);
        if (ret != 0)
        {
            printf("at32_write_memory error at: 0x%08X, len:%d\n", startAddr + wroteSize, thisWriteSize);
            return -1;
        }
        // b->delay_ms(5);
        wroteSize += thisWriteSize;
        SizeLeft -= thisWriteSize;
        stepPercent = wroteSize * 100 / totalSize;
        if (stepPercent != stepPercentPre)
        {
            stepPercentPre = stepPercent;
            printf(">");
            fflush(stdout);
        }
        if (SizeLeft == 0)
        {

            break;
        }
    }
    printf("\n");
    free(buff);

    if (packCount < 100)
    {
        for (int i = 0; i <= packCount; i++)
        {
            printf("=");
        }
        printf("\n");
    }
    else
    {
        for (int i = 0; i < 100; i++)
        {
            printf("=");
        }
        printf("\n");
    }
    printf("program done\n");
    return 0;
}
